{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 65,
   "id": "b8949d99-6bf5-4abb-a857-28c97f6667b9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from datetime import datetime\n",
    "import backtrader as bt\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import vectorbt as vbt\n",
    "\n",
    "df = pd.DataFrame(index=[datetime(2020, 1, i + 1) for i in range(9)])\n",
    "df['open'] = [1, 1, 2, 3, 4, 5, 6, 7, 8]\n",
    "df['high'] = df['open'] + 0.5\n",
    "df['low'] = df['open'] - 0.5\n",
    "df['close'] = df['open']\n",
    "data = bt.feeds.PandasData(dataname=df)\n",
    "size = np.array([5, 5, -5, -5, -5, -5, 5, 5, 0])\n",
    "\n",
    "\n",
    "class CommInfoFloat(bt.CommInfoBase):\n",
    "    \"\"\"Commission schema that keeps size as float.\"\"\"\n",
    "    params = (\n",
    "        ('stocklike', True),\n",
    "        ('commtype', bt.CommInfoBase.COMM_PERC),\n",
    "        ('percabs', True),\n",
    "      )\n",
    "    \n",
    "    def getsize(self, price, cash):\n",
    "        if not self._stocklike:\n",
    "            return self.p.leverage * (cash / self.get_margin(price))\n",
    "\n",
    "        return self.p.leverage * (cash / price)\n",
    "\n",
    "\n",
    "class CashValueAnalyzer(bt.analyzers.Analyzer):\n",
    "    \"\"\"Analyzer to extract cash and value.\"\"\"\n",
    "    def create_analysis(self):\n",
    "        self.rets = {}\n",
    "\n",
    "    def notify_cashvalue(self, cash, value):\n",
    "        self.rets[self.strategy.datetime.datetime()] = (cash, value)\n",
    "\n",
    "    def get_analysis(self):\n",
    "        return self.rets\n",
    "\n",
    "\n",
    "class TestStrategy(bt.Strategy):\n",
    "    def __init__(self):\n",
    "        self.i = 0\n",
    "        \n",
    "    def log(self, txt, dt=None):\n",
    "        dt = dt or self.data.datetime[0]\n",
    "        dt = bt.num2date(dt)\n",
    "        print('%s, %s' % (dt.isoformat(), txt))\n",
    "        \n",
    "    def notify_order(self, order):\n",
    "        if order.status in [bt.Order.Submitted, bt.Order.Accepted]:\n",
    "            return  # Await further notifications\n",
    "\n",
    "        if order.status == order.Completed:\n",
    "            if order.isbuy():\n",
    "                buytxt = 'BUY COMPLETE {}, size = {:.2f}, price = {:.2f}'.format(\n",
    "                    order.data._name, order.executed.size, order.executed.price)\n",
    "                self.log(buytxt, order.executed.dt)\n",
    "            else:\n",
    "                selltxt = 'SELL COMPLETE {}, size = {:.2f}, price = {:.2f}'.format(\n",
    "                    order.data._name, order.executed.size, order.executed.price)\n",
    "                self.log(selltxt, order.executed.dt)\n",
    "\n",
    "        elif order.status in [order.Expired, order.Canceled, order.Margin]:\n",
    "            self.log('%s ,' % order.Status[order.status])\n",
    "            pass  # Simply log\n",
    "\n",
    "        # Allow new orders\n",
    "        self.orderid = None\n",
    "        \n",
    "    def next(self):\n",
    "        if size[self.i] > 0:\n",
    "            self.buy(size=size[self.i])\n",
    "        elif size[self.i] < 0:\n",
    "            self.sell(size=-size[self.i])\n",
    "        self.i += 1\n",
    "\n",
    "def bt_simulate(shortcash):\n",
    "    cerebro = bt.Cerebro()\n",
    "    comminfo = CommInfoFloat(commission=0.01)\n",
    "    cerebro.broker.addcommissioninfo(comminfo)\n",
    "    cerebro.addstrategy(TestStrategy)\n",
    "    cerebro.addanalyzer(CashValueAnalyzer)\n",
    "    cerebro.broker.setcash(100.)\n",
    "    cerebro.broker.set_checksubmit(False)\n",
    "    cerebro.broker.set_shortcash(shortcash)\n",
    "    cerebro.adddata(data)\n",
    "    return cerebro.run()[0]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 66,
   "id": "f3b0b9e6-c91e-4c1d-a587-b95266bc24b1",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-01-02T00:00:00, BUY COMPLETE , size = 5.00, price = 1.00\n",
      "2020-01-03T00:00:00, BUY COMPLETE , size = 5.00, price = 2.00\n",
      "2020-01-04T00:00:00, SELL COMPLETE , size = -5.00, price = 3.00\n",
      "2020-01-05T00:00:00, SELL COMPLETE , size = -5.00, price = 4.00\n",
      "2020-01-06T00:00:00, SELL COMPLETE , size = -5.00, price = 5.00\n",
      "2020-01-07T00:00:00, SELL COMPLETE , size = -5.00, price = 6.00\n",
      "2020-01-08T00:00:00, BUY COMPLETE , size = 5.00, price = 7.00\n",
      "2020-01-09T00:00:00, BUY COMPLETE , size = 5.00, price = 8.00\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{datetime.datetime(2020, 1, 1, 0, 0): (100.0, 100.0),\n",
       " datetime.datetime(2020, 1, 2, 0, 0): (94.95, 99.95),\n",
       " datetime.datetime(2020, 1, 3, 0, 0): (84.85000000000001, 104.85000000000001),\n",
       " datetime.datetime(2020, 1, 4, 0, 0): (99.7, 114.7),\n",
       " datetime.datetime(2020, 1, 5, 0, 0): (119.5, 119.5),\n",
       " datetime.datetime(2020, 1, 6, 0, 0): (144.25, 119.25),\n",
       " datetime.datetime(2020, 1, 7, 0, 0): (173.95, 113.94999999999999),\n",
       " datetime.datetime(2020, 1, 8, 0, 0): (138.6, 103.6),\n",
       " datetime.datetime(2020, 1, 9, 0, 0): (98.19999999999999, 98.19999999999999)}"
      ]
     },
     "execution_count": 66,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "strategy = bt_simulate(True)\n",
    "strategy.analyzers.cashvalueanalyzer.get_analysis()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 67,
   "id": "e91645b8-9efe-468a-9154-d0928c9518e6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-01-01    100.00\n",
      "2020-01-02     94.95\n",
      "2020-01-03     84.85\n",
      "2020-01-04     99.70\n",
      "2020-01-05    119.50\n",
      "2020-01-06    144.25\n",
      "2020-01-07    173.95\n",
      "2020-01-08    138.60\n",
      "2020-01-09     98.20\n",
      "Name: close, dtype: float64\n",
      "2020-01-01    100.00\n",
      "2020-01-02     99.95\n",
      "2020-01-03    104.85\n",
      "2020-01-04    114.70\n",
      "2020-01-05    119.50\n",
      "2020-01-06    119.25\n",
      "2020-01-07    113.95\n",
      "2020-01-08    103.60\n",
      "2020-01-09     98.20\n",
      "Name: close, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "portfolio = vbt.Portfolio.from_orders(df.close, [np.nan] + size[:-1].tolist(), fees=0.01)\n",
    "print(portfolio.cash(free=False))\n",
    "print(portfolio.value())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 68,
   "id": "bf4257a3-511a-405a-a1fd-9d7250935f5f",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-01-02T00:00:00, BUY COMPLETE , size = 5.00, price = 1.00\n",
      "2020-01-03T00:00:00, BUY COMPLETE , size = 5.00, price = 2.00\n",
      "2020-01-04T00:00:00, SELL COMPLETE , size = -5.00, price = 3.00\n",
      "2020-01-05T00:00:00, SELL COMPLETE , size = -5.00, price = 4.00\n",
      "2020-01-06T00:00:00, SELL COMPLETE , size = -5.00, price = 5.00\n",
      "2020-01-07T00:00:00, SELL COMPLETE , size = -5.00, price = 6.00\n",
      "2020-01-08T00:00:00, BUY COMPLETE , size = 5.00, price = 7.00\n",
      "2020-01-09T00:00:00, BUY COMPLETE , size = 5.00, price = 8.00\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "{datetime.datetime(2020, 1, 1, 0, 0): (100.0, 100.0),\n",
       " datetime.datetime(2020, 1, 2, 0, 0): (94.95, 99.95),\n",
       " datetime.datetime(2020, 1, 3, 0, 0): (84.85000000000001, 104.85000000000001),\n",
       " datetime.datetime(2020, 1, 4, 0, 0): (99.7, 114.7),\n",
       " datetime.datetime(2020, 1, 5, 0, 0): (119.5, 119.5),\n",
       " datetime.datetime(2020, 1, 6, 0, 0): (94.25, 119.25),\n",
       " datetime.datetime(2020, 1, 7, 0, 0): (63.95, 113.95),\n",
       " datetime.datetime(2020, 1, 8, 0, 0): (83.60000000000001, 103.60000000000001),\n",
       " datetime.datetime(2020, 1, 9, 0, 0): (98.2, 98.2)}"
      ]
     },
     "execution_count": 68,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "strategy = bt_simulate(False)\n",
    "strategy.analyzers.cashvalueanalyzer.get_analysis()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 69,
   "id": "e936ecb7-4878-4d9f-97ba-675292e95a47",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2020-01-01    100.00\n",
      "2020-01-02     94.95\n",
      "2020-01-03     84.85\n",
      "2020-01-04     99.70\n",
      "2020-01-05    119.50\n",
      "2020-01-06     94.25\n",
      "2020-01-07     63.95\n",
      "2020-01-08     83.60\n",
      "2020-01-09     98.20\n",
      "Name: close, dtype: float64\n",
      "2020-01-01    100.00\n",
      "2020-01-02     99.95\n",
      "2020-01-03    104.85\n",
      "2020-01-04    114.70\n",
      "2020-01-05    119.50\n",
      "2020-01-06    119.25\n",
      "2020-01-07    113.95\n",
      "2020-01-08    103.60\n",
      "2020-01-09     98.20\n",
      "Name: close, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "print(portfolio.cash(free=True))\n",
    "print(portfolio.value())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6690767c-70f6-4ff2-859e-979b3bd83f2f",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
